/*
作者：吴咏炜
链接：https://www.zhihu.com/question/533559296/answer/2495020217
来源：知乎
著作权归作者所有。商业转载请联系作者获得授权，非商业转载请注明出处。
*/

#include <iostream>
#include <memory>
#include <string>
#include <utility>
#include <variant>

namespace lisp {

namespace impl {

class nil;
class atom;
class list;

void print(const nil&);
void print(const atom&);
void print(const list&);

}  // namespace impl

using object = std::variant<impl::nil, impl::list, impl::atom>;
using pointer = std::shared_ptr<const object>;

void print(const pointer& data);

namespace impl {

class nil {};

class atom {
public:
    // Non-explicit
    atom(int value) : content_(value) {}
    atom(double value) : content_(value) {}
    atom(const char* name) : content_(std::string(name)) {}
    atom(std::string name) : content_(std::move(name)) {}

    friend void print(const atom&);

private:
    std::variant<int, double, std::string> content_;
};

class list {
public:
    list(pointer head, pointer tail)
        : head_(std::move(head)), tail_(std::move(tail))
    {}

    pointer head() const
    {
        return head_;
    }
    pointer tail() const
    {
        return tail_;
    }

    friend void print(const list&);

private:
    pointer head_;
    pointer tail_;
};

void print(const nil&)
{
    std::cout << "NIL";
}

void print(const atom& data)
{
    std::visit(
        [](const auto& content) {
            std::cout << content;
        },
        data.content_);
}

void print(const list& data)
{
    std::cout << "(";
    lisp::print(data.head_);
    std::cout << ", ";
    lisp::print(data.tail_);
    std::cout << ")";
}

}  // namespace impl

void print(const pointer& ptr)
{
    std::visit([](const auto& data) { print(data); }, *ptr);
}

auto nil()
{
    return std::make_shared<object>();
}

auto cons(pointer head, pointer tail)
{
    return std::make_shared<object>(
        impl::list(std::move(head), std::move(tail)));
}

auto cons(impl::atom head, pointer tail)
{
    return std::make_shared<object>(impl::list(
        std::make_shared<object>(std::move(head)), std::move(tail)));
}

auto car(pointer lst)
{
    return std::get<impl::list>(*lst).head();
}

auto cdr(pointer lst)
{
    return std::get<impl::list>(*lst).tail();
}

}  // namespace lisp

int main()
{
    using lisp::car;
    using lisp::cdr;
    using lisp::cons;
    using lisp::nil;
    using lisp::print;

    print(cons(3, cons("abc", cons(3.3, nil()))));
    std::cout << std::endl;
    print(car(cons(3, cons("abc", cons(3.3, nil())))));
    std::cout << std::endl;
    print(cdr(cons(3, cons("abc", cons(3.3, nil())))));
    std::cout << std::endl;
    print(car(cdr(cons(3, cons("abc", cons(3.3, nil()))))));
    std::cout << std::endl;
}